/*
 * Copyright (C) 2024 Huawei Device Co., Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

import commonEvent from '@ohos.commonEvent';
import marsnapi from 'libmarsnapi.so';
import WakerLock from "./WakerLock";
import TreeSet from '@ohos.util.TreeSet';
import Log from '../xlog/Log'
import process from '@ohos.process';

/**
 * 定时器工具类，mars会在网络组件stn中使用定时器管理任务队列、连接间隔等
 * Created by caoshaokun on 16/3/7.
 */
// 定时器功能在ets无对应无法移植该类
class Alarm {
  private static readonly TAG: string = "MicroMsg.Alarm";
  private static readonly KEXTRA_ID: string = "ID";
  private static readonly KEXTRA_PID: string = "PID";
  private static readonly PUBLISH_EVENT: string = "publish_event" + process.pid;
  private static wakerlock: WakerLock = null;
  private static subscriber = null

  public static onAlarm(id: number): number {
    Log.i(Alarm.TAG, 'onAlarm enter id is ' + id)
    var result = marsnapi.Alarm_onAlarm(id);
    Log.i(Alarm.TAG, "onAlarm result " + result);
    return result
  }

  static ComparatorAlarm = class {
    public compare(lhs: any[], rhs: any[]): number {
      return (Number)((Number)(lhs[Alarm.TSetData.ID.valueOf()]) - (Number)(rhs[Alarm.TSetData.ID.valueOf()]));
    }
  }
  private static alarm_waiting_set: TreeSet<any[]> = new TreeSet();

  public static resetAlarm(): void {
    Alarm.alarm_waiting_set.forEach(data => {
      Log.i(Alarm.TAG, 'resetAlarm data is ' + JSON.stringify(data))
      Alarm.cancelAlarmMgr(data[Alarm.TSetData.PENDINGINTENT.valueOf()])
    })
    if (Alarm.alarm_waiting_set.length > 0) {
      Alarm.alarm_waiting_set.clear()
    }
    if (Alarm.subscriber != null) {
      commonEvent.unsubscribe(Alarm.subscriber)
      Alarm.subscriber = null
    }
  }

  public static start(id: number, after: number): boolean {
    // 没有对应取得设备启动时间(含休眠)
    let curTime: number = Date.now();

    if (0 > after) {
      console.error(Alarm.TAG, "id:%d, after:%d", id, after)
      return false;
    }

    if (null == Alarm.wakerlock) {
      Alarm.wakerlock = new WakerLock();
      Log.i(Alarm.TAG, "start new wakerlock");
    }

    if (null == Alarm.subscriber) {
      commonEvent.createSubscriber({ events: [Alarm.PUBLISH_EVENT] })
        .then(commonEventSubscriber => {
          Alarm.subscriber = commonEventSubscriber
          commonEvent.subscribe(Alarm.subscriber, (error, commonEventData) => {
            if (error.code) {
              console.error("start subscribe failed " + JSON.stringify(error));
            } else {
              Log.i(Alarm.TAG, 'start commonEventData is ' + JSON.stringify(commonEventData))
              Alarm.onReceive(commonEventData)
            }
          })
        }).catch(err => {
        console.error('start createSubscriber failed err: ' + JSON.stringify(err))
      })
    }
    let length = Alarm.alarm_waiting_set.length
    if (length > 0) {
      let count = 0
      let needAdd = true
      Alarm.alarm_waiting_set.forEach((data) => {
        count++
        Log.i(Alarm.TAG, 'start data is ' + JSON.stringify(data))
        if (data[Alarm.TSetData.ID.valueOf()] == id) {
          needAdd = false
        }
        if (length == count && needAdd) {
          Log.i(Alarm.TAG, 'add data')
          return Alarm.add(id, after, curTime)
        }
      })
      return false;
    } else {
      Log.i(Alarm.TAG, 'start else enter add data')
      return Alarm.add(id, after, curTime)
    }
  }

  private static add(id: number, after: number, curTime: number): boolean {
    let waitTime: number = after >= 0 ? curTime + after : curTime;
    let timerId: number = Alarm.setAlarmMgr(id, waitTime)
    Alarm.alarm_waiting_set.add([id, waitTime, timerId])
    return true
  }

  public static stop(id: number): boolean {
    Log.i(Alarm.TAG, 'stop enter id is ' + id)
    if (null == Alarm.wakerlock) {
      Alarm.wakerlock = new WakerLock();
      Log.i(Alarm.TAG, "stop new wakerlock")
    }

    if (null == Alarm.subscriber) {
      commonEvent.createSubscriber({ events: [Alarm.PUBLISH_EVENT] })
        .then(commonEventSubscriber => {
          Alarm.subscriber = commonEventSubscriber
          commonEvent.subscribe(Alarm.subscriber, (error, commonEventData) => {
            if (error.code) {
              Log.e(Alarm.TAG, "stop subscribe failed " + JSON.stringify(error));
            } else {
              Log.i(Alarm.TAG, "stop subscribe commonEventData is " + JSON.stringify(commonEventData));
              Alarm.onReceive(commonEventData)
            }
          })
        }).catch(err => {
        Log.e(Alarm.TAG, 'stop createSubscriber failed err: ' + JSON.stringify(err))
      })
    }

    let length = Alarm.alarm_waiting_set.length
    if (length > 0) {
      Alarm.alarm_waiting_set.forEach((data) => {
        Log.i(Alarm.TAG, 'stop data is ' + JSON.stringify(data))
        if (data[Alarm.TSetData.ID.valueOf()] == id) {
          Alarm.cancelAlarmMgr(data[Alarm.TSetData.PENDINGINTENT.valueOf()])
          return true
        }
      })
      return false
    } else {
      return false
    }
  }

  private static setAlarmMgr(id: number, time: number): number {
    Log.i(Alarm.TAG, 'setAlarmMgr enter id is ' + id + ', time is ' + time)
    let timerId: number = setTimeout(() => {
      let options = {
        data: id.toString(),
        isOrdered: true
      }
      commonEvent.publish(Alarm.PUBLISH_EVENT, options, () => {
        Log.i(Alarm.TAG, 'setAlarmMgr publish event callback')
      })
    }, time - Date.now())
    Log.i(Alarm.TAG, 'setAlarmMgr timerId is ' + timerId)
    return timerId
  }

  private static cancelAlarmMgr(timerId: number): boolean {
    Log.i(Alarm.TAG, 'cancelAlarmMgr enter timerId is ' + timerId)
    clearTimeout(timerId)
    let result: boolean = true
    Alarm.subscriber.abortCommonEvent()
      .then(() => {
        Log.i(Alarm.TAG, 'cancelAlarmMgr success')
      }).catch(err => {
      result = false
      Log.e(Alarm.TAG, 'cancelAlarmMgr failed ' + JSON.stringify(err))
    })
    return result;
  }

  private static onReceive(commonEventData) {
    Log.i(Alarm.TAG, 'onReceive enter commonEventData is ' + JSON.stringify(commonEventData))
    let id = commonEventData.data

    let found: boolean = false
    let foundData = undefined
    let length: number = Alarm.alarm_waiting_set.length
    if (length > 0) {
      Log.i(Alarm.TAG, 'onReceive length is ' + length)
      Alarm.alarm_waiting_set.forEach((data) => {
        Log.i(Alarm.TAG, 'for data is ' + JSON.stringify(data))
        let currentId: number = data[Alarm.TSetData.ID.valueOf()]
        if (currentId.toString() == id) {
          found = true
          foundData = data
        }
      })
      if (foundData != undefined && Alarm.alarm_waiting_set.has(foundData)) {
        Alarm.alarm_waiting_set.remove(foundData)
      }
    }

    if (!found) {
      console.error('onReceive not found')
    }

    if (null != Alarm.wakerlock) {
      Alarm.wakerlock.lock(200);
    }
    Log.i(Alarm.TAG, 'onReceive found is ' + found)
    if (found) {
      Alarm.onAlarm(parseInt(id));
    }
  }
}

namespace Alarm {
  export enum TSetData {
    ID,
    WAITTIME,
    PENDINGINTENT,
  }
}

export default Alarm;